#include "CrossValidation.h"

// Divide o conjunto de dados em k sub-amostras
CrossValidation::CrossValidation(uint k, DataSet& cDS) :
        ds(cDS)
{
    this->k = k;
    group = new uint[ds.size()];

    uint *count = new uint[k]; // Contador de dados por grupo
    uint gSize = ds.size() / k; // Quantidade ideal de dados para cada grupo

    // Iniciliza o contador
    for(uint j = 0; j < k; j++)
        count[j] = 0;

    srand(time(NULL)); // Inicializa a semente

    for(uint i = 0; i < ds.size(); i++) // Para cada entrada/saída
    {
        uint r = rand() % k; // Escolhe um grupo aleatoriamente
        bool done = false;

        // Tenta colocar no grupo selecionado randomicamente; se não conseguir, tenta no próximo grupo
        for(uint j = 0; j < k; j++)
        {
            uint indexGroup = (r + j) % k;

            if(count[indexGroup] < gSize) // Ainda já espaço neste grupo
            {
                group[i] = indexGroup;
                count[indexGroup]++;
                done = true;
                break;
            }
        }

        if(done)
            continue;

        // Caso todos os grupos estão cheios, tentaremos colocar naquele menos cheio
        for(uint j = 0; j < k; j++)
        {
            uint indexGroup = (r + j) % k;

            // Tenta colocar no grupo que está menos cheio
            if(count[indexGroup] == gSize)
            {
                group[i] = indexGroup;
                count[indexGroup]++;
                break;
            }
        }
    }

    delete[] count;
}

CrossValidation::~CrossValidation()
{
    delete[] group;
}

// Preenche os dados de treinamento e de teste para uma certa iteração
void CrossValidation::fillDataSets(DataSet& training, DataSet& test, uint i)
{
    for(uint j = 0; j < ds.size(); j++)
    {
        if(group[j] == i)
            test.addData(ds.get(j));
        else
            training.addData(ds.get(j));
    }
}

// Executa o algoritmo de CrossValidation para o perceptron passado
void CrossValidation::execute(Perceptron& p, uint maxIt)
{
    double rateSum = 0;
    double trainingTime = 0, testTime = 0;

    for(uint i = 0; i < k; i++)
    {
        DataSet *training, *test;
        training = new DataSet(ds.getNumberOfInVars(), ds.getNumberOfOutVars());
        test = new DataSet(ds.getNumberOfInVars(), ds.getNumberOfOutVars());

        // Divide as subamostras em amostras de treinamento e teste
        fillDataSets(*training, *test, i);

        p.train(*training, maxIt);
        trainingTime += training->getTime();
        p.test(*test);
        testTime += test->getTime();

        rateSum += test->getSuccessRate();

        delete training;
        delete test;

        p.resetNeuronsWeights();
    }

    double avgRate = rateSum / (double) k;

    ds.setSuccessRate(avgRate);
    cout << "AVERAGE SUCCESS RATE = " << avgRate << endl;
    cout << "TOTAL TRAINING TIME = " << trainingTime << " msec" << endl;
    cout << "TOTAL TEST TIME = " << testTime << " msec" << endl << endl;
    ds.setTime(trainingTime + testTime);
}

